package com.example.blenderandroid;

import android.util.Log;
import android.view.MotionEvent;

/**
 * Specialized class for processing a MotionEvent to determine 3D navigation gestures, including pan, zoom, and rotate.
 * Pan and zoom gestures are based on the first two pointers that touch the screen. Moving them along relatively the same
 * vector will trigger a pan event, moving them apart will trigger a zoom out, and moving them together will trigger a zoom
 * in. Rotation can be performed along two different axes. This is best visualized by imagining a line between the first two
 * pointers. Rotation parallel to the 3D viewport is achieved by rotating this axis around a line normal to the viewport, i.e.
 * by rotating the first two pointers in a circular motion. Rotation can be done around this virtual axis through the addition
 * of a third pointer. Moving this pointer away from the virtual axis rotates around this axis in one direction, and toward it
 * the other direction. Any pointers added past the third pointer are ignored.
 * 
 * @author Dale Bassett
 */

public class Navigation3DGestureDetector {
	private static final int INVALID_POINTER = -1;
	
	private OnNavigation3DGesture m_Listener;
	
	private int m_FirstPtr, m_SecondPtr, m_ThirdPtr;
	
	private float m_LastPtr1X, m_LastPtr1Y;
	private float m_LastPtr2X, m_LastPtr2Y;
	private float m_LastPtr3X, m_LastPtr3Y;
	
	/** The average distance between pointers during the last touch event */
	private float m_LastSpan;
	
	/** The center point of all pointers during the last touch event */
	private float m_LastFocalPointX, m_LastFocalPointY;
	
	private float m_LastParallelAngle;
	
	public Navigation3DGestureDetector(OnNavigation3DGesture listener) {
		m_Listener = listener;
		clearLastData();
	}
	
	private void clearLastData() {
		m_FirstPtr = INVALID_POINTER;
		m_SecondPtr = INVALID_POINTER;
		m_ThirdPtr = INVALID_POINTER;
		
		m_LastPtr1X = Float.NaN;
		m_LastPtr1Y = Float.NaN;
		m_LastPtr2X = Float.NaN;
		m_LastPtr2Y = Float.NaN;
		m_LastPtr3X = Float.NaN;
		m_LastPtr3Y = Float.NaN;
		
		m_LastSpan = Float.NaN;
		m_LastFocalPointX = Float.NaN;
		m_LastFocalPointY = Float.NaN;
		m_LastParallelAngle = Float.NaN;
	}
	
	public boolean onTouchEvent(MotionEvent evt) {
		int action = evt.getActionMasked();

		int ptrIndex = evt.getActionIndex();
		int ptrId = evt.getPointerId(ptrIndex);
		switch (action) {
		case MotionEvent.ACTION_DOWN:
			clearLastData();
			
			m_FirstPtr = ptrId;
			m_LastPtr1X = evt.getX(ptrIndex);
			m_LastPtr1Y = evt.getY(ptrIndex);

//			Log.i("Debug", "First Pointer Down, ID=" + ptrId);
			return true;
		case MotionEvent.ACTION_POINTER_DOWN:
			// assign the new pointer to the first unassigned pointer
			if (m_FirstPtr == INVALID_POINTER) {
				m_FirstPtr = ptrId;
				m_LastPtr1X = evt.getX(ptrIndex);
				m_LastPtr1Y = evt.getY(ptrIndex);
//				Log.i("Debug", "First Pointer Down, ID=" + ptrId);
			} else if (m_SecondPtr == INVALID_POINTER) {
				m_SecondPtr = ptrId;
				m_LastPtr2X = evt.getX(ptrIndex);
				m_LastPtr2Y = evt.getY(ptrIndex);
//				Log.i("Debug", "Second Pointer Down, ID=" + ptrId);
			} else if (m_ThirdPtr == INVALID_POINTER) {
				m_ThirdPtr = ptrId;
				m_LastPtr3X = evt.getX(ptrIndex);
				m_LastPtr3Y = evt.getY(ptrIndex);
//				Log.i("Debug", "Third Pointer Down, ID=" + ptrId);
			}
			
			return true;
		case MotionEvent.ACTION_MOVE:
			// update the position all pointers
			if (m_FirstPtr != INVALID_POINTER) {
				int ptr1Index = evt.findPointerIndex(m_FirstPtr);
				m_LastPtr1X = evt.getX(ptr1Index);
				m_LastPtr1Y = evt.getY(ptr1Index);
			}
			
			if (m_SecondPtr != INVALID_POINTER) {
				int ptr2Index = evt.findPointerIndex(m_SecondPtr);
				m_LastPtr2X = evt.getX(ptr2Index);
				m_LastPtr2Y = evt.getY(ptr2Index);
			}
			
			if (m_ThirdPtr != INVALID_POINTER) {
				int ptr3Index = evt.findPointerIndex(m_ThirdPtr);
				m_LastPtr3X = evt.getX(ptr3Index);
				m_LastPtr3Y = evt.getY(ptr3Index);
			}
			
			return true;
		case MotionEvent.ACTION_UP:
			clearLastData();

//			Log.i("Debug", "All pointers up");
			return true;
		case MotionEvent.ACTION_POINTER_UP:
			if (m_FirstPtr == ptrId) {
				m_FirstPtr = INVALID_POINTER;
//				Log.i("Debug", "First Pointer Up");
				m_LastPtr1X = Float.NaN;
				m_LastPtr1Y = Float.NaN;
			} else if (m_SecondPtr == ptrId) {
				m_SecondPtr = INVALID_POINTER;
//				Log.i("Debug", "Second Pointer Up");
				m_LastPtr2X = Float.NaN;
				m_LastPtr2Y = Float.NaN;
			} else if (m_ThirdPtr == ptrId) {
				m_ThirdPtr = INVALID_POINTER;
				m_LastPtr3X = Float.NaN;
				m_LastPtr3Y = Float.NaN;
//				Log.i("Debug", "Third Pointer Up");
			}
			return true;
		case MotionEvent.ACTION_CANCEL:
			clearLastData();
			
//			Log.i("Debug", "Action Canceled");
			return true;
		default:
			return false;
		}
	}

	public float getPointer1X() {
		return m_LastPtr1X;
	}

	public float getPointer1Y() {
		return m_LastPtr1Y;
	}

	public float getPointer2X() {
		return m_LastPtr2X;
	}

	public float getPointer2Y() {
		return m_LastPtr2Y;
	}

	public float getPointer3X() {
		return m_LastPtr3X;
	}

	public float getPointer3Y() {
		return m_LastPtr3Y;
	}





	public static interface OnNavigation3DGesture {
		public boolean OnZoom(Navigation3DGestureDetector detector, float zoomFactor);
		public boolean OnRotate(Navigation3DGestureDetector detector, float parallelAngle, float perpendicularRotate);
		public boolean OnPan(Navigation3DGestureDetector detector, float xDistance, float yDistance);
	}
}
